/*
  author: Jpeng
  email: peng8350@gmail.com
  time:2019-7-26 3:30
*/

library flutter_gifimage;

import 'dart:ui' as ui;
import 'dart:ui';

import 'package:flutter/widgets.dart';

import 'gif_image.dart';

/// cache gif fetched image
class GifCache {
  final Map<String, List<ImageInfo>> caches = Map();

  void clear() {
    caches.clear();
  }

  bool evict(Object key) {
    final List<ImageInfo>? pendingImage = caches.remove(key);
    if (pendingImage != null) {
      return true;
    }
    return false;
  }
}

/// controll gif
class GifController extends AnimationController {
  GifController(
      {required TickerProvider vsync,
      double value = 0.0,
      Duration reverseDuration = const Duration(milliseconds: 3000),
      Duration duration = const Duration(milliseconds: 3000),
      AnimationBehavior? animationBehavior})
      : super.unbounded(
            value: value,
            reverseDuration: reverseDuration,
            duration: duration,
            animationBehavior: animationBehavior ?? AnimationBehavior.normal,
            vsync: vsync);

  @override
  void reset() {
// TODO: implement reset
    value = 0.0;
  }


}

class GifImage extends StatefulWidget {
  const GifImage({
    super.key,
    required this.image,
    required this.controller,
    this.semanticLabel,
    this.excludeFromSemantics = false,
    this.width,
    this.height,
    this.onFetchCompleted,
    this.color,
    this.colorBlendMode,
    this.fit,
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
  });

  final VoidCallback? onFetchCompleted;
  final GifController controller;
  final AssetImage image;
  final double? width;
  final double? height;
  final Color? color;
  final BlendMode? colorBlendMode;
  final BoxFit? fit;
  final AlignmentGeometry alignment;
  final ImageRepeat repeat;
  final Rect? centerSlice;
  final bool matchTextDirection;
  final bool gaplessPlayback;
  final String? semanticLabel;
  final bool excludeFromSemantics;

  @override
  State<StatefulWidget> createState() {
    return GifImageState();
  }

  static GifCache cache = GifCache();
}

class GifImageState extends State<GifImage> {
  List<ImageInfo> _infos = [];
  int _curIndex = 0;
  bool _fetchComplete = false;

  ImageInfo? get _imageInfo {
    if (!_fetchComplete) return null;
    return _infos.isEmpty ? null : _infos[_curIndex];
  }

  @override
  void initState() {
    super.initState();
    widget.controller.addListener(_listener);
  }

  @override
  void dispose() {
    super.dispose();
    widget.controller.removeListener(_listener);
  }

  @override
  void didUpdateWidget(GifImage oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.image != oldWidget.image) {
      fetchGif(widget.image).then((imageInfo) {
        if (mounted) {
          setState(() {
            _infos = imageInfo ?? [];
            _fetchComplete = true;
            _curIndex = widget.controller.value.toInt();
            if (widget.onFetchCompleted != null) {
              widget.onFetchCompleted!();
            }
          });
        }
      });
    }
    if (widget.controller != oldWidget.controller) {
      oldWidget.controller.removeListener(_listener);
      widget.controller.addListener(_listener);
    }
  }

  void _listener() {
    if (_curIndex != widget.controller.value && _fetchComplete) {
      if (mounted) {
        setState(() {
          if (widget.controller.value < _infos.length) {
            _curIndex = widget.controller.value.toInt();
          }
        });
      }
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (_infos.isEmpty) {
      fetchGif(widget.image).then((imageInfo) {
        if (mounted) {
          setState(() {
            _infos = imageInfo ?? [];
            _fetchComplete = true;
            _curIndex = widget.controller.value.toInt();
            if (widget.onFetchCompleted != null) {
              widget.onFetchCompleted!();
            }
          });
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_imageInfo != null) {
      final RawImage image = RawImage(
        image: _imageInfo?.image,
        width: widget.width,
        height: widget.height,
        scale: _imageInfo?.scale ?? 1.0,
        color: widget.color,
        colorBlendMode: widget.colorBlendMode,
        fit: widget.fit,
        alignment: widget.alignment,
        repeat: widget.repeat,
        centerSlice: widget.centerSlice,
        matchTextDirection: widget.matchTextDirection,
      );
      if (widget.excludeFromSemantics) return image;
      return Semantics(
        container: widget.semanticLabel != null,
        image: true,
        label: widget.semanticLabel ?? '',
        child: image,
      );
    } else {
      return const SizedBox.shrink();
    }
  }
}

Future<List<ImageInfo>?> fetchGif(AssetImage provider) async {
  List<ImageInfo>? info = [];
  String cacheKey = provider.assetName;
  if (GifImage.cache.caches.containsKey(cacheKey)) {
    info = GifImage.cache.caches[cacheKey];
    return info;
  }
  AssetBundleImageKey key =
      await provider.obtainKey(const ImageConfiguration());
  _loadAsync(key,
          decode: PaintingBinding.instance.instantiateImageCodecWithSize)
      .then((value) async {
    for (int i = 0; i < value.frameCount; i++) {
      FrameInfo frameInfo = await value.getNextFrame();
      info?.add(ImageInfo(image: frameInfo.image));
    }
  });
  GifImage.cache.caches.putIfAbsent(cacheKey, () => info!);
  return info;
}

Future<ui.Codec> _loadAsync(
  AssetBundleImageKey key, {
  required SimpleDecoderCallback decode,
}) async {
  final ui.ImmutableBuffer buffer;
  try {
    buffer = await key.bundle.loadBuffer(key.name);
  } on FlutterError {
    PaintingBinding.instance.imageCache.evict(key);
    rethrow;
  }
  return decode(buffer);
}
